#define _CRT_SECURE_NO_WARNINGS  

#include "openssl/evp.h"  
#include <string.h>  
#include <stdio.h>  

#define FAILURE -1  
#define SUCCESS 0  

// Function to print hexadecimal data  
void print_hex(const char *title, const unsigned char *data, int len) {  
    printf("%s:", title);  
    for (int i = 0; i < len; i++) {  
        if (i % 16 == 0)  
            printf("\n%04X: ", i);  
        printf("%02X ", data[i]);  
    }  
    printf("\n");  
}  

int do_encrypt(const EVP_CIPHER *type, const char *ctype)  
{  
    unsigned char outbuf[1024];  
    int outlen, tmplen;  
    
    // SM4 uses a 16-byte (128-bit) key  
    unsigned char key[] = {  
        0x60, 0x3D, 0xEB, 0x10,   
        0x15, 0xCA, 0x71, 0xBE,   
        0x2B, 0x73, 0xAE, 0xF0,   
        0x85, 0x7D, 0x77, 0x81  
    };  
    
    // SM4 block size is 16 bytes, so IV should also be 16 bytes  
    unsigned char iv[] = {  
        0x00, 0x01, 0x02, 0x03,   
        0x04, 0x05, 0x06, 0x07,   
        0x08, 0x09, 0x0A, 0x0B,   
        0x0C, 0x0D, 0x0E, 0x0F  
    };  
    
    char intext[] = "Helloworld"; // Plaintext  
    EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();  
    if (!ctx) {  
        printf("Failed to create EVP_CIPHER_CTX\n");  
        return FAILURE;  
    }  
    
    // Initialize encryption operation  
    if (EVP_EncryptInit_ex(ctx, type, NULL, key, iv) != 1) {  
        printf("EVP_EncryptInit_ex failed for %s\n", ctype);  
        EVP_CIPHER_CTX_free(ctx);  
        return FAILURE;  
    }  
    
    // Provide plaintext and get ciphertext  
    if (EVP_EncryptUpdate(ctx, outbuf, &outlen, (unsigned char*)intext, strlen(intext)) != 1) {  
        printf("EVP_EncryptUpdate failed for %s\n", ctype);  
        EVP_CIPHER_CTX_free(ctx);  
        return FAILURE;  
    }  
    
    // Finalize encryption  
    if (EVP_EncryptFinal_ex(ctx, outbuf + outlen, &tmplen) != 1) {  
        printf("EVP_EncryptFinal_ex failed for %s\n", ctype);  
        EVP_CIPHER_CTX_free(ctx);  
        return FAILURE;  
    }  
    
    outlen += tmplen;  
    
    // Clean up context  
    EVP_CIPHER_CTX_free(ctx);  
    
    // Write ciphertext to file  
    char filename[64];  
    sprintf(filename, "./cipher_%s.dat", ctype);  
    FILE *out = fopen(filename, "wb+");  
    if (!out) {  
        printf("Cannot open file: %s\n", filename);  
        return FAILURE;  
    }  
    fwrite(outbuf, 1, outlen, out);  
    fflush(out);  
    fclose(out);  
    
    printf("%s encryption successful. Ciphertext length: %d bytes\n", ctype, outlen);  
    print_hex("Ciphertext", outbuf, outlen);  
    
    return SUCCESS;  
}  

int do_decrypt(const EVP_CIPHER *type, const char *ctype)  
{  
    unsigned char inbuf[1024] = { 0 };  
    unsigned char outbuf[1024] = { 0 };  
    int outlen, inlen, tmplen;  
    
    // SM4 uses a 16-byte (128-bit) key  
    unsigned char key[] = {  
        0x60, 0x3D, 0xEB, 0x10,   
        0x15, 0xCA, 0x71, 0xBE,   
        0x2B, 0x73, 0xAE, 0xF0,   
        0x85, 0x7D, 0x77, 0x81  
    };  
    
    // SM4 block size is 16 bytes, so IV should also be 16 bytes  
    unsigned char iv[] = {  
        0x00, 0x01, 0x02, 0x03,   
        0x04, 0x05, 0x06, 0x07,   
        0x08, 0x09, 0x0A, 0x0B,   
        0x0C, 0x0D, 0x0E, 0x0F  
    };  
    
    EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();  
    if (!ctx) {  
        printf("Failed to create EVP_CIPHER_CTX\n");  
        return FAILURE;  
    }  
    
    // Initialize decryption operation  
    if (EVP_DecryptInit_ex(ctx, type, NULL, key, iv) != 1) {  
        printf("EVP_DecryptInit_ex failed for %s\n", ctype);  
        EVP_CIPHER_CTX_free(ctx);  
        return FAILURE;  
    }  
    
    // Open the encrypted file  
    char filename[64];  
    sprintf(filename, "./cipher_%s.dat", ctype);  
    FILE *in = fopen(filename, "rb");  
    if (!in) {  
        printf("Cannot open file: %s\n", filename);  
        EVP_CIPHER_CTX_free(ctx);  
        return FAILURE;  
    }  
    
    // Read ciphertext  
    inlen = fread(inbuf, 1, sizeof(inbuf), in);  
    fclose(in);  
    printf("%s decryption: Read ciphertext length: %d bytes\n", ctype, inlen);  
    
    // Provide ciphertext and get plaintext  
    if (EVP_DecryptUpdate(ctx, outbuf, &outlen, inbuf, inlen) != 1) {  
        printf("EVP_DecryptUpdate failed for %s\n", ctype);  
        EVP_CIPHER_CTX_free(ctx);  
        return FAILURE;  
    }  
    
    // Finalize decryption  
    if (EVP_DecryptFinal_ex(ctx, outbuf + outlen, &tmplen) != 1) {  
        printf("EVP_DecryptFinal_ex failed for %s\n", ctype);  
        EVP_CIPHER_CTX_free(ctx);  
        return FAILURE;  
    }  
    
    outlen += tmplen;  
    
    // Clean up context  
    EVP_CIPHER_CTX_free(ctx);  
    
    printf("%s decryption result: \n%.*s\n", ctype, outlen, outbuf);  
    print_hex("Decrypted plaintext (hex)", outbuf, outlen);  
    
    return SUCCESS;  
}  

int main(int argc, char *argv[])  
{  
    // Initialize OpenSSL algorithms  
    OpenSSL_add_all_algorithms();  
    
    // Use SM4-CBC for encryption and decryption  
    const EVP_CIPHER *sm4_cbc = EVP_sm4_cbc();  
    if (!sm4_cbc) {  
        printf("Cannot get SM4-CBC cipher\n");  
        return FAILURE;  
    }  
    
    printf("===== SM4-CBC Encryption and Decryption =====\n");  
    if (do_encrypt(sm4_cbc, "sm4-cbc") != SUCCESS) {  
        printf("SM4-CBC encryption failed\n");  
        return FAILURE;  
    }  
    
    if (do_decrypt(sm4_cbc, "sm4-cbc") != SUCCESS) {  
        printf("SM4-CBC decryption failed\n");  
        return FAILURE;  
    }  
    
    // Clean up OpenSSL algorithms  
    EVP_cleanup();  
    
    return 0;  
}